using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.VisualStudio.Text;

namespace PostscriptTools {
	/// <summary>
	/// Handy reusable utility to listen for change events on the associated buffer, but
	/// only pass these along to a set of listeners when the user stops typing for a specified interval
	/// </summary>
	public class LazyEventHandler<TEventArgs> : IDisposable where TEventArgs: EventArgs {
		private event EventHandler<TEventArgs> OriginalEventHandler;
		private Stopwatch _stopwatch;
		private readonly int _millisecondsBetweenCalls;
		private readonly string _forWho;
		private long _firstTimeTook;
		private bool _allCallbacksMustBeDone;
		private Timer _timer;


		public LazyEventHandler(EventHandler<TEventArgs> originalEventHandler, int millisecondsBetweenCalls, string forWho, bool allCallbacksMustBeDone) {
			this._millisecondsBetweenCalls = millisecondsBetweenCalls;
			this.OriginalEventHandler = originalEventHandler;
			_forWho = forWho;
			_allCallbacksMustBeDone = allCallbacksMustBeDone;
		}

		public EventHandler<TEventArgs> EventHandler {
			get { return LazyEventHandlerRoutine;}
		}

		public void Dispose() {
			this._stopwatch = null;
			OriginalEventHandler = null;
		}

		public void OneArgOriginalEventHandler(object o) {
			var arg = o as Tuple<object, TEventArgs>;
			if (arg != null) {
				OriginalEventHandler(arg.Item1, arg.Item2);
			}
		}



		public void LazyEventHandlerRoutine(object src, TEventArgs args){
			if(OriginalEventHandler == null) {
				return; // ?Dispose has been called?
			}
			if ((_timer != null) && !_allCallbacksMustBeDone) {
				_timer.Dispose(); // kill the existing one
				_timer = null;
			}
			if (this._stopwatch == null) {
				// ok, we have defintely not yet been called
				this._stopwatch = new Stopwatch();
				_stopwatch.Start();
				OriginalEventHandler(src, args);
			} else {
				if (this._stopwatch.ElapsedMilliseconds < this._millisecondsBetweenCalls) {
					// do not call originaleventhandler immediately, but queue for idle time
					if (_allCallbacksMustBeDone) {
						int WaitBeforeCalling = Math.Max(_millisecondsBetweenCalls, (int) _firstTimeTook);
						_timer = new System.Threading.Timer(OneArgOriginalEventHandler, new Tuple<object, TEventArgs>(src, args), WaitBeforeCalling, Timeout.Infinite);
					}
				} else {
					_stopwatch.Restart();
					OriginalEventHandler(src, args);
				}
			}
			if (_firstTimeTook == 0) {
				_firstTimeTook = _stopwatch.ElapsedMilliseconds;
			}
			this._stopwatch.Restart();
		}
	}
}
